#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <misc/debug.h>
#include <misc/offset.h>
#include <base/BLog.h>
#include <ncd/NCDValueParser.h>
#include <ncd/NCDValueGenerator.h>

#include "IfListController.h"

#include <generated/blog_channel_IfListController.h>

#define ICON_ERROR "dialog-error"
#define ICON_NOINET "network-disconnect"
#define ICON_HAVEINET "network-connect"

static int parse_value_str (const char *str, NCDValue *out);
static void read_addrs (NCDValue *list, char **out_display);
static void read_routes (NCDValue *list, char **out_display, int *has_default_gw);
static void read_status (NCDValue *arg, char **out_display);
static struct IfListController_interface * interface_new (IfListController *o, const char *ifname);
static void interface_free (struct IfListController_interface *i);
static struct IfListController_interface * find_interface (IfListController *o, const char *ifname);
static void start_interface_enum (IfListController *o);
static void end_interface_enum (IfListController *o);
static void handle_error (IfListController *o);
static void srvuser_handler_up (IfListController *o, NCDRequestClient *client);
static void srvuser_func_down (IfListController *o);
static void req_handler_sent (IfListController *o);
static void req_handler_reply (IfListController *o, NCDValue reply_data);
static void req_handler_finished (IfListController *o, int is_error);
static void update_tray_icon (IfListController *o);

static int parse_value_str (const char *str, NCDValue *out)
{
    if (!NCDValueParser_Parse(str, strlen(str), out)) {
        BLog(BLOG_ERROR, "NCDValueParser_Parse failed");
        return 0;
    }
    
    return 1;
}

static void read_addrs (NCDValue *list, char **out_display)
{
    ExpString str;
    ASSERT_FORCE(ExpString_Init(&str))
    
    if (!list || NCDValue_Type(list) != NCDVALUE_LIST) {
        goto out;
    }
    
    for (NCDValue *eval = NCDValue_ListFirst(list); eval; eval = NCDValue_ListNext(list, eval)) {
        if (NCDValue_Type(eval) != NCDVALUE_MAP) {
            continue;
        }
        
        NCDValue *addr_val = NCDValue_MapFindValueByString(eval, "addr");
        NCDValue *prefix_val = NCDValue_MapFindValueByString(eval, "prefix");
        if (!addr_val || !prefix_val || NCDValue_Type(addr_val) != NCDVALUE_STRING || NCDValue_Type(prefix_val) != NCDVALUE_STRING) {
            continue;
        }
        
        const char *addr = NCDValue_StringValue(addr_val);
        const char *prefix = NCDValue_StringValue(prefix_val);
        
        if (eval != NCDValue_ListFirst(list)) {
            ASSERT_FORCE(ExpString_Append(&str, " "))
        }
        
        char buf[512];
        snprintf(buf, sizeof(buf), "%s/%s", addr, prefix);
        ASSERT_FORCE(ExpString_Append(&str, buf));
    }
    
out:
    *out_display = ExpString_Get(&str);
}

static void read_routes (NCDValue *list, char **out_display, int *has_default_gw)
{
    ExpString str;
    ASSERT_FORCE(ExpString_Init(&str))
    
    *has_default_gw = 0;
    
    if (!list || NCDValue_Type(list) != NCDVALUE_LIST) {
        goto out;
    }
    
    for (NCDValue *eval = NCDValue_ListFirst(list); eval; eval = NCDValue_ListNext(list, eval)) {
        if (NCDValue_Type(eval) != NCDVALUE_MAP) {
            continue;
        }
        
        NCDValue *addr_val = NCDValue_MapFindValueByString(eval, "addr");
        NCDValue *prefix_val = NCDValue_MapFindValueByString(eval, "prefix");
        NCDValue *gateway_val = NCDValue_MapFindValueByString(eval, "gateway");
        if (!addr_val || !prefix_val || !gateway_val || NCDValue_Type(addr_val) != NCDVALUE_STRING || NCDValue_Type(prefix_val) != NCDVALUE_STRING ||
            NCDValue_Type(gateway_val) != NCDVALUE_STRING) {
            continue;
        }
        
        const char *addr = NCDValue_StringValue(addr_val);
        const char *prefix = NCDValue_StringValue(prefix_val);
        const char *gateway = NCDValue_StringValue(gateway_val);
        
        if (eval != NCDValue_ListFirst(list)) {
            ASSERT_FORCE(ExpString_Append(&str, ", "))
        }
        
        char buf[512];
        if (!strcmp(gateway, "none")) {
            snprintf(buf, sizeof(buf), "%s/%s", addr, prefix);
        } else {
            snprintf(buf, sizeof(buf), "%s/%s via %s", addr, prefix, gateway);
        }
        ASSERT_FORCE(ExpString_Append(&str, buf));
        
        if (!strcmp(addr, "0.0.0.0") && !strcmp(prefix, "0")) {
            *has_default_gw = 1;
        }
    }
    
out:
    *out_display = ExpString_Get(&str);
}

static void read_status (NCDValue *arg, char **out_display)
{
    if (!arg || NCDValue_Type(arg) != NCDVALUE_STRING) {
        goto fail;
    }
    
    *out_display = strdup(NCDValue_StringValue(arg));
    return;
    
fail:
    *out_display = strdup("unknown");
}

static struct IfListController_interface * interface_new (IfListController *o, const char *ifname)
{
    ASSERT(!find_interface(o, ifname))
    
    struct IfListController_interface *i = malloc(sizeof(*i));
    i->cntrl = o;
    i->ifname = strdup(ifname);
    i->status = NULL;
    i->has_default_gw = 0;
    IfListEntry_Init(&i->entry, o->iflist, ifname);
    LinkedList1_Append(&o->interfaces_list, &i->interfaces_list_node);
    
    return i;
}

static void interface_free (struct IfListController_interface *i)
{
    LinkedList1_Remove(&i->cntrl->interfaces_list, &i->interfaces_list_node);
    IfListEntry_Free(&i->entry);
    free(i->status);
    free(i->ifname);
    free(i);
}

static struct IfListController_interface * find_interface (IfListController *o, const char *ifname)
{
    for (LinkedList1Node *ln = LinkedList1_GetFirst(&o->interfaces_list); ln; ln = LinkedList1Node_Next(ln)) {
        struct IfListController_interface *i = UPPER_OBJECT(ln, struct IfListController_interface, interfaces_list_node);
        ASSERT(i->cntrl == o)
        if (!strcmp(i->ifname, ifname)) {
            return i;
        }
    }
    
    return NULL;
}

static void start_interface_enum (IfListController *o)
{
    for (LinkedList1Node *ln = LinkedList1_GetFirst(&o->interfaces_list); ln; ln = LinkedList1Node_Next(ln)) {
        struct IfListController_interface *i = UPPER_OBJECT(ln, struct IfListController_interface, interfaces_list_node);
        ASSERT(i->cntrl == o)
        i->is_enumed = 0;
    }
}

static void end_interface_enum (IfListController *o)
{
    LinkedList1Node *next;
    for (LinkedList1Node *ln = LinkedList1_GetFirst(&o->interfaces_list); ln; ln = next) {
        next = LinkedList1Node_Next(ln);
        struct IfListController_interface *i = UPPER_OBJECT(ln, struct IfListController_interface, interfaces_list_node);
        ASSERT(i->cntrl == o)
        if (!i->is_enumed) {
            interface_free(i);
        }
    }
}

static void handle_error (IfListController *o)
{
    if (o->have_request) {
        NCDRequestClientRequest_Free(&o->req);
        o->have_request = 0;
    }
    
    update_tray_icon(o);
}

static void srvuser_handler_up (IfListController *o, NCDRequestClient *client)
{
    DebugObject_Access(&o->d_obj);
    ASSERT(!o->have_request)
    
    NCDValue rval;
    if (!parse_value_str("{\"netevent\"}", &rval)) {
        goto fail;
    }
    
    if (!NCDRequestClientRequest_Init(&o->req, client, &rval, o,
        (NCDRequestClientRequest_handler_sent)req_handler_sent,
        (NCDRequestClientRequest_handler_reply)req_handler_reply,
        (NCDRequestClientRequest_handler_finished)req_handler_finished
    )) {
        BLog(BLOG_ERROR, "NCDRequestClientRequest_Init failed");
        NCDValue_Free(&rval);
        goto fail;
    }
    
    NCDValue_Free(&rval);
    
    o->have_request = 1;
    
    update_tray_icon(o);
    return;
    
fail:
    handle_error(o);
}

static void srvuser_func_down (IfListController *o)
{
    DebugObject_Access(&o->d_obj);
    
    handle_error(o);
}

static void req_handler_sent (IfListController *o)
{
    DebugObject_Access(&o->d_obj);
    ASSERT(o->have_request)
}

static void req_handler_reply (IfListController *o, NCDValue reply_data)
{
    DebugObject_Access(&o->d_obj);
    ASSERT(o->have_request)
    
    start_interface_enum(o);
    
    if (NCDValue_Type(&reply_data) != NCDVALUE_MAP) {
        goto out;
    }
    
    for (NCDValue *ekey = NCDValue_MapFirstKey(&reply_data); ekey; ekey = NCDValue_MapNextKey(&reply_data, ekey)) {
        NCDValue *eval = NCDValue_MapKeyValue(&reply_data, ekey);
        
        if (NCDValue_Type(ekey) != NCDVALUE_STRING || NCDValue_Type(eval) != NCDVALUE_MAP) {
            continue;
        }
        
        NCDValue *type_val = NCDValue_MapFindValueByString(eval, "type");
        NCDValue *link_val = NCDValue_MapFindValueByString(eval, "link");
        NCDValue *addrs_val = NCDValue_MapFindValueByString(eval, "addrs");
        NCDValue *routes_val = NCDValue_MapFindValueByString(eval, "routes");
        NCDValue *status_val = NCDValue_MapFindValueByString(eval, "status");
        if (!type_val || !link_val || NCDValue_Type(type_val) != NCDVALUE_STRING || NCDValue_Type(link_val) != NCDVALUE_STRING) {
            continue;
        }
        
        const char *ifname = NCDValue_StringValue(ekey);
        const char *type_str = NCDValue_StringValue(type_val);
        const char *link_str = NCDValue_StringValue(link_val);
        
        char *addrs_str;
        char *routes_str;
        char *status_str;
        int has_default_gw;
        
        read_addrs(addrs_val, &addrs_str);
        read_routes(routes_val, &routes_str, &has_default_gw);
        read_status(status_val, &status_str);
        
        int link_is_up = !strcmp(link_str, "up");
        int is_inet = !strcmp(status_str, "finished") && has_default_gw;
        
        struct IfListController_interface *i = find_interface(o, ifname);
        if (!i) {
            i = interface_new(o, ifname);
        }
        
        free(i->status);
        i->status = strdup(status_str);
        i->has_default_gw = has_default_gw;
        IfListEntry_SetData(&i->entry, type_str, link_is_up, addrs_str, routes_str, status_str, is_inet);
        i->is_enumed = 1;
        
        free(addrs_str);
        free(routes_str);
        free(status_str);
    }
    
out:
    end_interface_enum(o);
    update_tray_icon(o);
    NCDValue_Free(&reply_data);
}

static void req_handler_finished (IfListController *o, int is_error)
{
    DebugObject_Access(&o->d_obj);
    ASSERT(o->have_request)
    
    BLog(BLOG_ERROR, "request error/finished!?");
    
    handle_error(o);
}

static void update_tray_icon (IfListController *o)
{
    const char *icon;
    const char *msg;
    char buf[128];
    
    if (!o->have_request) {
        icon = ICON_ERROR;
        msg = "Error communicating with NCD";
    } else {
        icon = ICON_NOINET;
        msg = "No Internet access";
        
        for (LinkedList1Node *ln = LinkedList1_GetFirst(&o->interfaces_list); ln; ln = LinkedList1Node_Next(ln)) {
            struct IfListController_interface *i = UPPER_OBJECT(ln, struct IfListController_interface, interfaces_list_node);
            ASSERT(i->cntrl == o)
            if (i->status && !strcmp(i->status, "finished") && i->has_default_gw) {
                icon = ICON_HAVEINET;
                snprintf(buf, sizeof(buf), "Internet access via %s", i->ifname);
                msg = buf;
                break;
            }
        }
    }
    
    gtk_status_icon_set_from_icon_name(o->tray_icon, icon);
    gtk_status_icon_set_tooltip(o->tray_icon, msg);
}

void IfListController_Init (IfListController *o, ServerInterface *srvif, IfList *iflist, GtkStatusIcon *tray_icon)
{
    o->iflist = iflist;
    o->tray_icon = tray_icon;
    LinkedList1_Init(&o->interfaces_list);
    ServerUser_Init(&o->srvuser, srvif, o, (ServerUser_handler_up)srvuser_handler_up, (ServerUser_func_down)srvuser_func_down);
    o->have_request = 0;
    
    update_tray_icon(o);
    
    DebugObject_Init(&o->d_obj);
}

void IfListController_Free (IfListController *o)
{
    DebugObject_Free(&o->d_obj);
    
    LinkedList1Node *ln;
    while (ln = LinkedList1_GetFirst(&o->interfaces_list)) {
        struct IfListController_interface *i = UPPER_OBJECT(ln, struct IfListController_interface, interfaces_list_node);
        ASSERT(i->cntrl == o)
        interface_free(i);
    }
    
    if (o->have_request) {
        NCDRequestClientRequest_Free(&o->req);
    }
    
    ServerUser_Free(&o->srvuser);
}
